package me.ezjs.framework.core.dao.hibernate;

import me.ezjs.framework.core.dao.GenericDAO;
import me.ezjs.framework.core.model.DAOFilter;
import me.ezjs.framework.core.model.FlipFilter;
import me.ezjs.framework.core.model.Operate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.orm.ObjectRetrievalFailureException;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;

/**
 * genericDAO接口的hibernate实现
 * Created by zero-mac on 16/3/10.
 */
public class GenericDaoHibernate<T, ID extends Serializable> implements GenericDAO<T, ID> {

    protected final Log log = LogFactory.getLog(getClass());

    protected boolean ALLOW_QUERY_CACHE = false;//允许查询缓存
    protected final static String QUERY_CACHE_REGION = "query.daoList";


    private Class<T> persistentClass;
    protected String entityName;

    @Resource
    private SessionFactory sessionFactory;

    /**
     * Constructor that takes in a class to see which type of entity to persist. Use this constructor when
     * subclassing.
     *
     * @param persistentClass the class type you'd like to persist
     */
    public GenericDaoHibernate(final Class<T> persistentClass) {
        this.persistentClass = persistentClass;
        this.entityName = persistentClass.getSimpleName();
    }

    /**
     * Constructor that takes in a class and sessionFactory for easy creation of DAO.
     *
     * @param persistentClass the class type you'd like to persist
     * @param sessionFactory  the pre-configured Hibernate SessionFactory
     */
    public GenericDaoHibernate(final Class<T> persistentClass, SessionFactory sessionFactory) {
        this(persistentClass);
        this.sessionFactory = sessionFactory;
    }

    public SessionFactory getSessionFactory() {
        return this.sessionFactory;
    }

    public Session getSession() throws HibernateException {
        Session session = getSessionFactory().getCurrentSession();
        if (session == null) {
            session = getSessionFactory().openSession();
        }
        return session;
    }

    @Autowired
    @Required
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    @Override
    public T get(ID id) {
        Session se = getSession();
        IdentifierLoadAccess byId = se.byId(persistentClass);
        T entity = (T) byId.load(id);//返回代理对象,延迟加载

        if (entity == null) {
            log.warn("'" + this.persistentClass + "' object with id '" + id + "' not found...");
            throw new ObjectRetrievalFailureException(this.persistentClass, id);
        }

        return entity;
    }

    @Override
    public T get(String fieldName, Object val) {
        DAOFilter filter = new DAOFilter();
        filter.addSearch(fieldName, Operate.EQUAL, val);
        return get(filter);
    }

    @Override
    public T get(DAOFilter filter) {
        List<T> list = this.list(filter);
        if (list == null || list.size() == 0) {
            return null;
        }
        return list.get(0);
    }

    @Override
    public boolean exists(ID id) {
        return get(id) != null;
    }

    @Override
    public T save(T object) {
        Session se = getSession();
        return (T) se.merge(object);//同时支持save和update
    }

    @Override
    public void remove(T object) {
        Session se = getSession();
        se.delete(object);
    }

    @Override
    public void remove(ID id) {
        Session se = getSession();
        se.delete(get(id));
    }

    private AliasGenerator initAliasGenerator(DAOFilter filter) {
        AliasGenerator aliasGenerator = new AliasGenerator(entityName);
        aliasGenerator.analyse(filter);
        return aliasGenerator;
    }

    @Override
    public int remove(DAOFilter filter) {
        StringBuilder hqlBuilder = new StringBuilder(256);
        AliasGenerator aliasGenerator = initAliasGenerator(filter);


        hqlBuilder.append("delete ").append(aliasGenerator.getFromClause());
        hqlBuilder.append(aliasGenerator.getWhereClause());


        final String hql = hqlBuilder.toString();

        Session session;
        int count;

        session = getSession();
        Query query = session.createQuery(hql);
        aliasGenerator.bindParameters4Filter(query, filter);
        count = query.executeUpdate();

        return count;
    }

    @Override
    public long count(DAOFilter filter) {
        StringBuilder hqlBuilder = new StringBuilder(256);
        AliasGenerator aliasGenerator = initAliasGenerator(filter);

        Session session = getSession();
        Query query;

        hqlBuilder.append("select count(*) ").append(aliasGenerator.getFromClause());
        hqlBuilder.append(aliasGenerator.getWhereClause());
        final String hqlCount = hqlBuilder.toString();
        log.debug("count hql:" + hqlCount);

        query = session.createQuery(hqlCount);
        aliasGenerator.bindParameters4Filter(query, filter);
        cacheQuery(query);
        Object result = query.uniqueResult();
        if (result instanceof Number) {
            return ((Number) result).intValue();
        }
        return 0;
    }

    @Override
    public List<T> list(DAOFilter filter) {
        StringBuilder hqlBuilder = new StringBuilder(256);
        AliasGenerator aliasGenerator = initAliasGenerator(filter);

        hqlBuilder.append("select distinct " + aliasGenerator.getEntityAlias())
                .append(aliasGenerator.getFromClause());
        hqlBuilder.append(aliasGenerator.getWhereClause());
        hqlBuilder.append(aliasGenerator.getOrderClause());
        final String hql = hqlBuilder.toString();
        log.debug("list hql:" + hql);

        Session session = getSession();
        Query query = session.createQuery(hql);
        aliasGenerator.bindParameters4Filter(query, filter);
        cacheQuery(query);
        return query.list();
    }

    @Override
    public List<T> flipList(FlipFilter filter) {
        return flipList(filter, true);
    }

    @Override
    public List<T> flipList(FlipFilter filter, boolean distinct) {
        int start = validateFlip(filter);
        StringBuffer hqlBuilder = new StringBuffer(256);
        Session session = getSession();
        Query query;
        AliasGenerator ag = initAliasGenerator(filter);

        if (filter.getTotalCount() == 0) {
            hqlBuilder.append("select count(*) ").append(ag.getFromClause());
            hqlBuilder.append(ag.getWhereClause());
            final String hqlCount = hqlBuilder.toString();
            log.debug("flip list count hql: " + hqlCount);

            query = session.createQuery(hqlCount);
            ag.bindParameters4Filter(query, filter);
            cacheQuery(query);
            Object result = query.uniqueResult();
            if (result instanceof Number) {
                filter.setTotalCount(((Number) result).intValue());
            }
            if (filter.getTotalCount() == 0) {
                return new LinkedList<>();
            }
        }
        filter.initFlipInfo();

        // 拼接分页语句
        hqlBuilder.setLength(0);
        if (distinct) {
            hqlBuilder.append("select distinct " + ag.getEntityAlias() + " ")
                    .append(ag.getFromClause());
        } else {
            hqlBuilder.append("select " + ag.getEntityAlias() + " ").append(ag.getFromClause());
        }
        hqlBuilder.append(ag.getWhereClause());
        hqlBuilder.append(ag.getOrderClause());
        final String hqlSearch = hqlBuilder.toString();
        if (log.isDebugEnabled()) {
            log.debug("flip search hql:" + hqlSearch);
        }

        query = session.createQuery(hqlSearch);
        query.setFirstResult(start);
        query.setMaxResults(filter.getPageSize());
        ag.bindParameters4Filter(query, filter);
        cacheQuery(query);
        return query.list();
    }

    protected final static int validateFlip(FlipFilter filter) {
        if (filter.getPageNo() < 1) {
            // change to default instead.
            filter.setPageNo(1);
        }
        if (filter.getPageSize() <= 0 || filter.getPageSize() > FlipFilter.MAX_PAGESIZE) {
            filter.setPageSize(FlipFilter.DEFAULT_PAGESIZE);
        }
        return (filter.getPageNo() - 1) * filter.getPageSize();
    }

    @Override
    public List<T> getAll() {
        Session sess = getSession();
        return sess.createCriteria(persistentClass).list();
    }

    private void cacheQuery(Query query) {
        if (ALLOW_QUERY_CACHE) {
            query.setCacheable(true);
            query.setCacheRegion(QUERY_CACHE_REGION);
        }
    }
}
